import Express from "express";
import session from "express-session";
import passport from "passport";
import { Strategy as LocalStrategy } from "passport-local";
import { getInstance } from "../common";
import { dir } from "../node/electron-utils";
import DefaultRoutes from "./session-utils-routes";
// @ts-ignore
import betterSqlite3SessionStore from "better-sqlite3-session-store";
import Sqlite3 from "better-sqlite3";
/**
 * 默认的User内容，从session中获取当前登录用户信息时使用
 */
// export interface User {
//   [key: string]: any;
// }

export interface LoginRes<User> {
  success: boolean;
  user?: User;
}

// declare module "express-session" {
//   interface SessionData {
//     passport?: { user: User };
//   }
// }

/**
 * 登录时向服务器post的数据
 */
export interface LoginInput {
  /** 用户在登录时输入的username */
  username: string;
  /** 用户在登录时输入的password */
  password: string;
}

/**
 * 如果登录失败，告知passport的错误信息
 */
export type FailureFunction = () => string;

/**
 * 处理用户登录的同步或异步函数
 */
export interface HandleLogin</** 登录用户信息应该的样子 */ UserData> {
  (/** 登录时用户输入的 username 和 password */ loginInput: LoginInput):
    | Promise<UserData>
    | UserData
    | Promise<FailureFunction>
    | FailureFunction
    | undefined
    | Promise<undefined>;
}

export interface SetupPassport<UserData> {
  /** 向服务器提交登录请求的route */
  routeLogin?: string | RegExp;
  /** 向服务器提交登出请求的route */
  routeLogout?: string | RegExp;
  /** 获取当前登录用户数据的Route */
  routeUserInfo?: string | RegExp;
  /**
   * 处理username和password的函数
   * @return 如果登录成功，返回用户数据。否则返回一个undefined或函数，该函数没有参数，返回值为错误信息字符串，且认为登录失败
   */
  handleLogin: HandleLogin<UserData>;
}

/**
 * 默认存储session的数据库的路径
 */
export const default_session_db3_path = dir(
  "database/session/sessions.sqlite3",
  "file"
);

/**
 * 默认的session配置
 * @returns 返回一个session配置, 使用app.use()接受
 */
export function createSessionStore() {
  const db = new Sqlite3(default_session_db3_path);
  const SqliteStore = betterSqlite3SessionStore(session);
  const store = new SqliteStore({
    client: db,
    expired: {
      clear: true,
      intervalMs: 900000, //ms = 15min
    },
  });
  return session({
    secret: "keyboard cat",
    resave: false, // don't save session if unmodified//
    proxy: true, // enable use of req.session from client
    saveUninitialized: false, // don't create session until something stored//
    store: store,
  });
}

/**
 * 获取用户信息的函数
 * @param req 把req放进参数
 * @returns 用户信息
 */
export function getUser<User>(req: Express.Request): User | undefined {
  // @ts-ignore
  return req.session.passport?.user;
}

/**
 * 初始化使用用户名密码登录功能、以及获取当前登录用户信息的Router和middleware
 * 向服务器提交登录请求的字段必须为{ username, password }
 * @example // 定义User的类型
 * export interface User{
 *     // anything you want
 * }
 * declare module "express-session" {
 *      interface SessionData {
 *           passport?: { user: User };
 *      }
 * }
 * // if you post { username: string; password: string } to routeLogin,
 * // you will get { success: boolean, user: User }
 * @param param0 配置
 * @returns 返回一个Express.Router和passport.authenticate('local')
 */
export function createLoginRouter<User = any>({
  handleLogin,
  routeLogin = DefaultRoutes.login,
  routeLogout = DefaultRoutes.logout,
  routeUserInfo = DefaultRoutes.userInfo,
}: SetupPassport<User>) {
  const router = Express.Router();
  passport.serializeUser((user, done) => done(null, user));
  // @ts-ignore
  passport.deserializeUser((user, done) => done(null, user as User));
  passport.use(
    new LocalStrategy(async (username, password, done) => {
      const loginResult = await handleLogin({ username, password });
      if (/Object/i.test(getInstance(loginResult))) {
        return done(null, loginResult);
      } else if (/Function/i.test(getInstance(loginResult))) {
        const failureFunction = loginResult as FailureFunction;
        const message = await failureFunction();
        done(null, false, { message });
      } else {
        return done(null, false);
      }
    })
  );
  router.use(routeLogin, passport.authenticate("local"), (req, res) => {
    const response: LoginRes<User> = {
      // @ts-ignore
      success: !!req.session.passport?.user,
      // @ts-ignore
      user: req.session.passport?.user,
    };
    res.json(response);
  });
  router.use(routeLogout, (req, res) => {
    // @ts-ignore
    delete req.session.passport;
    res.json({ success: true });
  });
  // @ts-ignore
  router.use(routeUserInfo, (req, res) => res.json(req.session.passport?.user));
  return { router, middleware: passport.authenticate("local") };
}

export default { createSessionStore, loginOutRouter: createLoginRouter };
